# $Id: Question.pm,v 1.1 2004/02/21 18:48:07 ibo Exp $
package CGI::FOGE2::Question; # abstract
use strict;
our @ISA = ();

use CGI::FOGE2::Tools;
use CGI::FOGE2::TextField;
use CGI::FOGE2::TextArea;
use CGI::FOGE2::Menu;
use CGI::FOGE2::MultQuestion;

use CGI::FOGE2::Stats;

=head1 NAME

Question - an abstract class for FOGE question types.

=head1 DESCRIPTION

Mandatory parameters of C<new>:

=over 4

=item * title

The text of the question itself.

=item * id

A valid SQL column name that will be used to identify the question.

=back

Methods that all C<Question> subclasses should provide (methods
provided by C<Question> itself are marked with a star):

=over 4

=item * serve

Args: none.

Returns: the HTML needed to display this question. Typically three columns
of a table, containing title, ismandatory, input.

=item * handle_submission*

Args: a DBI query handler (already DBI::prepared), int ref.

Returns: Boolean (true indicates success).

Effect: Checks the question's answer for existence, checks that it
matches the question's regexp if any, that it passes the question's
predicate (if any). All tests are skipped for a non-mandatory
question.

On error, false is returned and an error message is stored, which can
be retrieved with errmsg();

The int ref indicates where values should be put in the prepared
SQL query. It is incremented appropriately (this integer is provided
by Section rather than by each Question because each Question of a
section fills a different part of the query).

=item * generate_field_names*

Args: none

Returns: an array ref containing the adequate number of ids. They are
strings suitable for use as HTML parameter names and as SQL column
names. The question's id is used if the question produces a single
value, otherwise this id is suffixed by a number.

=item * make_sql_structure*

Args: none

Returns: the SQL structure for this question.

=item * errmsg*

Returns the last error message, as stored by store_error.

=item * store_error*

Args: string.

Stores an error message.

=back

=cut

sub handle_submission {
    # args: DBI query handler, int ref
    # returns: boolean (success <-> true)

    my $self = shift;
    my ($STH) = @_;

    my $names = $self->generate_field_names();
    #use Data::Dumper;
    #print STDERR Dumper($names); # DEBUG

    $self->store_error("Success");
    foreach my $name (@$names) {
        # Exit as soon as an error is detected!
        #print STDERR "Question.pm: handle_submission: $name\n"; #DEBUG
	my $title = $self->{title};
	my $regexp = $self->{isvalidregexp};
	my $predicate = $self->{validate};

	my $val = CGI::param($name);
	unless ( defined $val ) {
            $self->store_error(sprintf msg->chk_missing, $title);
            return 0;
	}

	chomp $val;

	if (!$self->{ismandatory}) {
	    # nothing to do
	} else {
	    # complex tests (ne '', bof, regexp, predicate)
	    if ($val eq '') {
		$self->store_error
		    (sprintf msg->chk_foundempty, $title);
		return 0;
            #QNC: reference theform MOCHE
	    } elsif ($val eq theform->get_bof()) {
		$self->store_error
		    (sprintf msg->chk_foundbof, $title);
		return 0;
	    } elsif (defined $regexp && !($val =~ /$regexp/)) {
		$self->store_error
		    (sprintf msg->chk_regexpfail, $title);
		return 0;
	    } elsif (   defined $predicate
                 && (my $msg=(&$predicate($val))) ne '') {
                #QNC $msg useless ???
		$self->store_error
		    (sprintf msg->chk_predicatefail, $title);
		return 0;
	    }
	}
	$STH->add_param($val);
    }
    return 1;
}

sub generate_field_names {
    my $self = shift;
    my $id = $self->{id};
    return [ $id ];
}

sub store_error { # private
    my $self = shift;
    $self->{errmsg} = shift;
}

sub errormsg {
    my $self = shift;
    return $self->{errmsg};
}

sub ismandatory {
    my $self = shift;
    return $self->{ismandatory};
}

sub initstats {
    my $self = shift;
    my ($stats) = @_;
    foreach my $stat (@$stats) {
        $stat->setcolumns($self->generate_field_names());
    }
}

sub getstats {
    my $self = shift;
    my $out = '';
    foreach my $stat (@{$self->{stats}}) {
        $out .= $stat->getstats();
    }
    table3cols($self->{title}, $self->{ismandatory}, $out);
}

# Sort the result of stats->computedata() along the order suggested by
# the question itself. See CGI::FOGE2::Stats.pm
# This method is kind of abstract since it has to be specialized for 
# questions with an 'items' field (to display a menu or a row of button).

sub sort {
    my $self = shift;
    my ($number, $total, $names, $vals) = @_;
    return ($number, $total, $names, $vals);
}

sub _construct {                #protected
    my ($that, $args, $mandatory_keys) = @_;

    # Write the hash values we received into $that
    foreach my $key (keys %$args) {
        # Check that the hash we receive only has allowed key names
        if (exists $that->{$key}) {
            $that->{$key} = $args->{$key};
        } else {
            CGI::Carp::croak(sprintf msg->die_badkey, $key);
          }
    }

    # Check that all mandatory keys are present
    foreach my $key (@$mandatory_keys) {
        if (!defined $that->{$key}) {
            CGI::Carp::croak(sprintf msg->die_missingkey, $key);
          }
    }

    $that->initstats($that->{stats});
}

sub as_text {
    my $self = shift;
    my ($ign, $indent) = @_;
    my $out = $self->as_text_header(@_);

    $out .= $indent x 2 . "(contents are not listed for this question type)\n";
    return $out;
}

sub as_text_header {
    my $self = shift;
    my ($num, $indent) = @_;
    return sprintf "%s%s %s%s\n", $indent, list2str($num), 
	$self->{title}, $self->{ismandatory}?' '.msg->mandatory:'';
}

1;
